import multiprocessing
import time
from tracemalloc import start
import yaml
import json
import os
import logging
import random
from concurrent.futures import ThreadPoolExecutor,wait,ALL_COMPLETED
from utils.const import *
from utils.ac_result import ACResult, SUCCESS, FAILED
from queue import Queue
from threading import Thread
from lib.jenkins_proxy import JenkinsProxy
from lib.gitee_proxy import GiteeProxy

logger = logging.getLogger()

class Manager(object):
    """
    manger process
    """

    def __init__(self, api_conf, pr_num, repo_name, jenkins_base_url, jenkins_user, jenkins_token, gitee_owner, gitee_token):
        self._task_list = self.init_conf(api_conf)
        self._pr_num = pr_num
        self._repo_name = repo_name
        self._juser = jenkins_user
        self._jtoken = jenkins_token
        self._jenkins = JenkinsProxy(jenkins_base_url, jenkins_user, jenkins_token)
        self._gitee = GiteeProxy(gitee_owner, repo_name, gitee_token)
        self._up_builds = None
        self._ac_result = {}
        self._format_data = {}
        self._get_upstream_builds()
        return
    
    def init_conf(self, file_dir):
        try:
            with open(file_dir, 'r') as f:
                content = f.read()
                dict = yaml.safe_load(content)
                return dict.get("projects", [])
                
        except Exception as e:
            print("文件读取异常: %s", e)
            return {}
            

    def exec(self, param):
        """
        exec
        """
        if self.check_docs():
            self.send_to_gitee(data=None, is_docs=True)
            return

        def go_task(out_q ,conf, param):
            class_name = conf.get("type", "")
            if class_name == "":
                return None
            class_name = class_name.capitalize() + "Api"
            try:
                cl = createInstance("interface.comm_api", class_name, conf)
            except Exception as e:
                logger.error(e)
            task_id, url = cl.create_task(param)
            logger.info("now running: %s, url is : %s", conf["path"], url+"console")
            start = time.time()
            while True:
                time.sleep(random.randint(50,120))
                try:
                    task_res = cl.get_task_info({"task_id": task_id})
                    errno = task_res.get("errno")
                    if errno ==  ErrOk:
                        out_q.put(task_res.get("data"))
                        break
                    elif errno == ErrProcess:
                        continue
                    elif errno == ErrOther:
                        out_q.put({})
                        break
                except Exception as e:
                    logger.warn(str(e))
            end = time.time()
            logger.info("now %s is run over, duration: %f", conf["path"], (end-start))

        q = Queue()
        dserver = Thread(target=self.data_server, args=(q,))
        dserver.start()
        # 开启多线程
        cpu_count = multiprocessing.cpu_count()
        with ThreadPoolExecutor(max_workers = cpu_count) as t:
            all_task = []
            logger.info("task all start")
            for task_conf in self._task_list:
                task_conf["user"] = self._juser
                task_conf["token"] = self._jtoken
                all_task.append(t.submit(go_task, q, task_conf, param))
            wait(all_task, return_when = ALL_COMPLETED)
            logger.info("task all finisd")
        
        q.put(OverStr)
    
    
    def check_docs(self):
        commitfiles = self._gitee.get_commits_files(self._pr_num)
        for file in commitfiles:
            logger.info(file)
            if not file.startswith("docs"):
                return False
        logger.info("docs check true")
        self._gitee.comment_pr(pr=self._pr_num, comment="This pull request only submits documentation, so no builds will take place")
        return True

    def data_server(self, in_q):
        """
        data_server
        """
        result = []
        while True:
            data = in_q.get()
            if data == OverStr:
                break
            result.append(data)

        logger.info(result)
        for index, itemRes in enumerate(result):
            if itemRes['check_result'] == {}:
                comment = "one check had not any result, click follow link for detail: {}".format(itemRes["task_url"])
                self._gitee.comment_pr(self._pr_num, comment)
                del result[index]
                
        check_list = self.format_data(result)
        s_data = {"pr_num": self._pr_num, "repo_name": self._repo_name,"check_list": check_list}

        logger.info(s_data)
        
        # 处理gitee业务
        gitee_t = Thread(target=self.send_to_gitee, args = (s_data, ))
        gitee_t.start()
        # 处理databoard业务
        databoard_t = Thread(target=self.send_to_databoard, args = (s_data, ))
        databoard_t.start()

        return

    @staticmethod
    def format_data(data):
        check_list = []
        for item in data:
            check = {'task_log': item["task_log"], 'task_url': item["task_url"], 'task_no': item["task_no"]}
            split = item["check_result"].split(";")
            for sp_item in split:
                sp_item=sp_item.strip()
                if sp_item == "":
                    continue
                sp_item_split = sp_item.split("=")
                check[sp_item_split[0]]=sp_item_split[1]
            check_list.append(check)
        res = []
        for check in check_list:
            res_item = {}
            if "arch" in check:
                res_item["is_arch"] = True
                res_item["arch"] = check["arch"]
                res_item["image"] = check["image"]
                res_item["build_res"] = check["build_res"]
            else:
                res_item["is_arch"] = False
            check_detail = []
            for key,value in check.items():
                if key.startswith("check_"):
                    check_detail.append({key.lstrip("check_"): value})
            res_item["check_details"] = check_detail
            # res_item["task_log"] = check["task_log"]
            res_item["task_log"] = "this is a log"
            res_item["task_url"] = check["task_url"]
            res_item["task_no"] = check["task_no"]
            res.append(res_item)
        return res

    def send_to_gitee(self, data, is_docs = False):
        self.send_comment(data=data, is_docs=is_docs)
        self.send_label(data=data, is_docs=is_docs)
        
        
    def send_label(self, data, is_docs):
        self._gitee.delete_tag_of_pr(self._pr_num, "ci_processing")
        if not is_docs:
            check_all = self.check_all_result(data)
        else:
            check_all = self.check_acl_result()
        if check_all == SUCCESS:
            self._gitee.delete_tag_of_pr(self._pr_num, "ci_failed")
            self._gitee.create_tags_of_pr(self._pr_num, "ci_successful")
        else:
            self._gitee.delete_tag_of_pr(self._pr_num, "ci_successful")
            self._gitee.create_tags_of_pr(self._pr_num, "ci_failed")

    def send_comment(self, data, is_docs = False):
        comments = ["<table>", self.format_html_table_th()]
        ac_build = None
        if self._up_builds != None and len(self._up_builds) > 0:
            ac_build = self._up_builds[0]
        acl = self.get_acl_from_build(ac_build)
        comments.extend(self.format_html_of_ac(acl=acl))
        if not is_docs:
            comments.extend(self.format_html_of_construct(data))
        comments.append("</table>")       

        res = self._gitee.comment_pr(self._pr_num, "\n".join(comments))
        
        logger.info(res)

    def check_all_result(self, data):
        # code check
        if self.check_acl_result() != SUCCESS:
            return FAILED
        # build check
        if self.check_build_result(data) != SUCCESS:
            return FAILED
        # other check
        if self.check_other_result(data) != SUCCESS:
            return FAILED
        return SUCCESS

    def check_acl_result(self):
        if "ACL" not in os.environ:
            logger.debug("no ac check")
            return SUCCESS

        try:
            acl = json.loads(os.environ["ACL"])
            logger.debug("ac result: %s", acl)
        except ValueError:
            logger.exception("invalid ac result format")
            return FAILED
        except Exception as e:
            logger.exception(e)
            return FAILED
        
        for item in acl:
            # ci check res not contain sca check
            if item["name"] == "check_sca" or item["name"] == "check_package_license":
                continue
            ac_result = ACResult.get_instance(item["result"])
            if ac_result != SUCCESS:
                return FAILED
        return SUCCESS

    def check_build_result(self, data):
        """
        build result check
        :return:
        """
        build_result = SUCCESS
        for check in data.get("check_list", []):
            if check["is_arch"]:
                build_result = sum([build_result], ACResult.get_instance(check["build_res"]))

        return build_result

    def check_other_result(self, data):
        for check in data.get("check_list", []):
            for item in check.get("check_details", []):
                for _, value in item.items():
                    value = ACResult.get_instance(value)
                    if value != SUCCESS:
                        return FAILED

        return SUCCESS

    def get_acl_from_build(self, build):

        build_no = build["number"]
        job_path = build["fullDisplayName"]
        # 
        job_paths = job_path.split("#")[0]
        job_paths = job_paths.split("»")
        paths = []
        for path in job_paths:
            paths.append(path.strip())
        job_path = '/'.join(paths)
        # envMap = self._jenkins.get_build_env(job_path=job_path, build_no=build_no)["envMap"]
        acl = []
        try:
            acl = json.loads(os.environ["ACL"])
            logger.debug("ac result: %s", acl)
        except ValueError:
            logger.exception("invalid ac result format")
            return []
        except Exception as e:
            logger.exception(e)
            return []
        
        res_acl = []
        for item in acl:
            if item["name"] == "check_package_license":
                continue
            if item["name"] == "check_sca":
                if ACResult.get_instance(item["result"]) != SUCCESS:
                    item["result"] = "warning"
            res_acl.append(item)
        acl = res_acl

        if len(acl) > 0:
            item = acl[0]
            item["url"] = build["url"]
            item["number"] = build_no
            acl[0] = item
        return acl


    def _get_upstream_builds(self):
        """
        get upstream builds
        :param jenkins_proxy:
        :return:
        """
        base_job_name = os.environ.get("JOB_NAME")
        base_build_id = os.environ.get("BUILD_ID")
        base_build_id = int(base_build_id)
        logger.debug("base_job_name: %s, base_build_id: %s", base_job_name, base_build_id)
        base_build = self._jenkins.get_build_info(base_job_name, base_build_id)
        logger.debug("get base build")
        self._up_builds = self._jenkins.get_upstream_builds(base_build)

    def send_to_databoard(self, data):
        # response = requests.post("http://localhost:8080/api/data", json = data, headers={"token":self._board_token})
        print("go exec send_to_databoard")
        pass

    def format_html_of_ac(self, acl):
        """
        组装门禁检查结果
        :param build: Jenkins Build object，门禁检查jenkins构建对象
        :return:
        """

        comments = []

        for index, item in enumerate(acl):
            ac_result = ACResult.get_instance(item["result"])
            if index == 0:
                build_url = item["url"]
                comments.append(self.format_html_table_tr(
                    item["name"], ac_result.emoji, ac_result.hint,
                    "{}{}".format(build_url, "console"), item["number"], rowspan=len(acl)))
            else:
                comments.append(self.format_html_table_tr_rowspan(
                    item["name"], ac_result.emoji, ac_result.hint))
            self._ac_result[item["name"]] = ac_result.hint
        logger.info("ac comment: %s", comments)

        return comments

    def format_html_of_construct(self, data):
        """
        check item comment
        :param builds:
        :return:
        """
        tr_list = []
        check_list = self._construct_dict(data=data)
        for arch_name,arch_result in check_list.items():
            arch_name_rows_span = 0
            # arch_comment_dict = comment_file_dict.get(arch_name,{})
            is_first = True
            first_image = ""
            for image_name, image_result in arch_result.items():
                arch_name_rows_span += len(image_result.get("details",[])) + 1
                if is_first:
                    first_image = image_name
                    is_first = False
            for image_name, image_result in arch_result.items():
                image_name_rows_span = len(image_result.get("details", [])) + 1
                if image_name == first_image:
                    tr_list.append({
                        "arch_name": arch_name, 
                        "arch_name_rows_span": arch_name_rows_span,
                        "image_name": image_name, 
                        "image_name_rows_span": image_name_rows_span, 
                        "check_name": "build",
                        "status": image_result["build_res"], 
                        "build_url": image_result["build_url"], 
                        "build_no": image_result["build_no"]})
                    for item in image_result['details']:
                        tr_list.append({"check_name": item["check_name"],"status": item["status"]})
                else:
                    tr_list.append({
                        "image_name": image_name, 
                        "image_name_rows_span": image_name_rows_span, 
                        "check_name": "build",
                        "status": image_result["build_res"], 
                        "build_url": image_result["build_url"], 
                        "build_no": image_result["build_no"]})
                    
                    
                    # if "result" in image_result and ACResult.get_instance(image_result.get("result")) == SUCCESS:
                    for item in image_result["details"]:
                        tr_list.append({"check_name": item["check_name"], "status": item["status"]})

        comments = []
        for tr in tr_list:
            tr_str = "<tr>"
            if "arch_name" in tr:
                tr_str += "<td rowspan={}>{}</td>".format(tr.get("arch_name_rows_span"), tr.get("arch_name"))
            if "image_name" in tr:
                tr_str += "<td rowspan={}>{}</td>".format(tr.get("image_name_rows_span"), tr.get("image_name"))
            tr_str += "<td>{}</td>".format(tr.get("check_name"))
            ac_result = ACResult.get_instance(tr.get("status"))
            tr_str += "<td>{}<strong>{}</strong></td>".format(ac_result.emoji, ac_result.hint)
            if "build_url" in tr:
                tr_str += "<td rowspan={}><a href={}>#{}</a></td>".format(\
                    tr.get("image_name_rows_span"),\
                    "{}{}".format(tr.get("build_url"), "console"),\
                    tr.get("build_no"))
            tr_str += "</tr>"
            comments.append(tr_str)
        return comments

    def _construct_dict(self, data):
        res_dict = {}
        for check in data["check_list"]:
            current_name = check["arch"]
            current_arch = res_dict.get(current_name,{})
            image_name = check["image"]
            image_dict = current_arch.get(image_name, {})
            image_dict["build_no"] = check["task_no"]
            image_dict["build_url"] = check["task_url"]
            image_dict["build_res"] = check["build_res"]
            image_dict["details"] = []
            details = []
            for detail in check["check_details"]:
                for key,value in detail.items():
                    details.append({"check_name": key, "status": value})
            image_dict["details"].extend(details)
            current_arch[check["image"]] = image_dict
            res_dict[current_name] = current_arch
        
        return res_dict

    @classmethod
    def format_html_table_th(cls):
        """
        table header
        """
        return "<tr><th colspan=3>Check Name</th> <th>Build Result</th> <th>Build Details</th></tr>"
    
    @classmethod
    def format_html_table_tr(cls, name, icon, status, href, build_no, hashtag=True, rowspan=1):
        """
        one row or span row
        """
        return "<tr><td colspan=3>{}</td> <td>{}<strong>{}</strong></td> " \
               "<td rowspan={}><a href={}>{}{}</a></td></tr>".format(
            name, icon, status, rowspan, href, "#" if hashtag else "", build_no)
    
    @classmethod
    def format_html_table_tr_rowspan(cls, name, icon, status):
        """
        span row
        """
        return "<tr><td colspan=3>{}</td> <td>{}<strong>{}</strong></td></tr>".format(name, icon, status)

def createInstance(module_name, class_name, *args, **kwargs):

    module_meta = __import__(module_name, globals(), locals(), [class_name])

    class_meta = getattr(module_meta, class_name)

    obj = class_meta(*args, **kwargs)

    return obj